using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Presentation.Media;

using GHIElectronics.NETMF.Hardware;

using Skewworks.Pyxis.Drivers;
using Skewworks.Pyxis.EXT;
using Skewworks.Pyxis.GUI.Controls;

namespace Skewworks.Pyxis.Kernel
{
    internal static class TinyCLRForum
    {

        #region Structures

        private struct NetHeader
        {
            public string Key;
            public string Value;
            public NetHeader(string Key, string Value)
            {
                this.Key = Key;
                this.Value = Value;
            }
        }

        private struct NetResponse
        {
            public NetHeader[] Headers;
            public string Data;
        }

        #endregion

        #region Variables

        private static PyxisAPI API;
        private static bool bLoaded = false;
        private static MenuItem AppMenu = null;
        private static Form frmMain = null;
        private static MenuItem mnuQuitApp;
        private static MenuItem mnuSwitchToApp;
        private static Bitmap buffer2 = null;
        private static string _COOKIE;
        private static ForumListbox FLB;
        private static MessageListbox MLB;

        #endregion

        #region Public Methods

        public static void Show(PyxisAPI api)
        {

            API = api;
            if (API.desktop._mnu.count > 1) API.desktop._mnu.remove(1);

            if (!bLoaded)
            {
                ShowDialog("Loading TinyCLR Forum; Please Wait...");
                CreateApp();
                bLoaded = true;
                API.SetModalState(false);
                API.ScreenBuffer.DrawImage(0, 0, buffer2, 0, 0, buffer2.Width, buffer2.Height);
            }

            // Display App
            API.desktop._mnu.add(AppMenu);
            API.ActiveForm = frmMain;

        }

        public static void Quit()
        {
            if (!bLoaded) return;

            API.desktop.mnuRun.RemoveItem(mnuSwitchToApp);
            API.desktop.mnuForce.RemoveItem(mnuQuitApp);
            if (API.desktop.mnuForce.ItemCount == 0) API.desktop.mnuForce.Enabled = false;
            if (API.desktop.mnuRun.ItemCount == 0) API.desktop.mnuRun.Enabled = false;

            API.DisplayDesktop();

            frmMain = null;
            mnuQuitApp = null;
            mnuSwitchToApp = null;
            AppMenu = null;

            bLoaded = false;
        }

        #endregion

        #region Private Methods

        private static void CreateApp()
        {
            // Create Form
            frmMain = new Form(API, Colors.LightGray, true, true);

            // Create Application Menus
            AppMenu = new MenuItem("TinyCLR Forum");

            AppMenu.AddItem(new MenuItem("Log Out"));
            AppMenu.Item(0).Enabled = false;
            AppMenu.AddItem(new MenuItem("-"));
            AppMenu.AddItem(new MenuItem("Quit"));
            AppMenu.Item(2).tapEvent += new MenuItemTap((object sender) => Quit());

            // Create Switch/Quit Menus
            mnuSwitchToApp = new MenuItem("TinyCLR Forum");
            mnuSwitchToApp.tapEvent += new MenuItemTap((object sender) => Show(API));
            mnuSwitchToApp.Tag = "TinyCLR Forum";
            API.desktop.mnuRun.AddItem(mnuSwitchToApp);
            API.desktop.mnuRun.Enabled = true;

            mnuQuitApp = new MenuItem("TinyCLR Forum");
            mnuQuitApp.tapEvent += new MenuItemTap((object sender) => Quit());
            mnuQuitApp.Tag = "TinyCLR Forum";
            API.desktop.mnuForce.AddItem(mnuQuitApp);
            API.desktop.mnuForce.Enabled = true;

            // Load Settings
            if (!File.Exists(API.MyDrives.RootDirectory + "\\Apps\\TinyCLRForum\\user.db"))
            {
                LoginForm();
            }

        }

        private static void Login(Textbox txtEmail, Textbox txtPassword, Checkbox chkRemember, CommandButton cmdLogin)
        {
            txtEmail.Enabled = false;
            txtPassword.Enabled = false;
            chkRemember.Enabled = false;
            cmdLogin.Enabled = false;

            AppMenu.Text = "TinyCLR (Logging In)";

            // Create db
            if (chkRemember.Value)
            {
                //!!
            }

            // Grab login page
            NetResponse sRes = DownloadURL("http://www.tinyclr.com/login");

            // Get Session
            Debug.Print("Looking for Session ID");
            for (int i = 0; i < sRes.Headers.Length; i++)
            {
                if (sRes.Headers[i].Key.ToLower() == "set-cookie")
                {
                    _COOKIE = sRes.Headers[i].Value;
                    Debug.Print("COOKIES: " + _COOKIE);
                    break;
                }
            }

            // Login User
            string sPost = "action=login&error_url=/login/&from_domain=http://www.tinyclr.com&email=" + txtEmail.Text + "&password=" + txtPassword.Text;
            sRes = DoPost("http://www.tinyclr.com/process/account.php", sPost, null);

            if (sRes.Data == null)
            {
                // Attempt to find new session id
                for (int i = 0; i < sRes.Headers.Length; i++)
                {
                    if (sRes.Headers[i].Key.ToLower() == "set-cookie")
                    {
                        _COOKIE = sRes.Headers[i].Value;
                        Debug.Print("COOKIES: " + _COOKIE);
                        break;
                    }
                }

                // Look for a redirect
                for (int i = 0; i < sRes.Headers.Length; i++)
                {
                    if (sRes.Headers[i].Key.ToLower() == "location")
                    {
                        AppMenu.Text = "TinyCLR (Logged In)";
                        BoardList();
                        break;
                    }
                }
            }
            else
            {
                AppMenu.Text = "TinyCLR (Login Failed)";
                return;
            }

        }

        private static void LoginForm()
        {
            Panel pnl = new Panel(0, 0, 320, 106);

            pnl.AddChild(new Label("Email:", 4, 6));
            Textbox txtEmail = new Textbox("", 70, 4, 246, 20);
            pnl.AddChild(txtEmail);

            pnl.AddChild(new Label("Password:", 4, 30));
            Textbox txtPassword = new Textbox("", 70, 28, 246, 20);
            txtPassword.PasswordChar = '*';
            pnl.AddChild(txtPassword);


            Checkbox chkRemember = new Checkbox(70, 52);
            pnl.AddChild(chkRemember);
            pnl.AddChild(new Label("Remember Me", 92, 53));

            CommandButton cmdLogin = new CommandButton("Login", 123, 77);
            cmdLogin.Enabled = false;
            pnl.AddChild(cmdLogin);

            pnl.X = frmMain.Width / 2 - pnl.Width / 2;
            pnl.Y = frmMain.Height / 2 - pnl.Height / 2;

            // Bind Events
            txtEmail.TextChanged += new OnTextChanged((object sender) => TextChanged(txtEmail, txtPassword, cmdLogin));
            txtPassword.TextChanged += new OnTextChanged((object sender) => TextChanged(txtEmail, txtPassword, cmdLogin));
            cmdLogin.tapEvent += new OnTap((object sender, point e) => Login(txtEmail, txtPassword, chkRemember, cmdLogin));

            //!! Testing only
            txtEmail.Text = "seris7759@aol.com";
            txtPassword.Text = "FairePlay09";

            frmMain.AutoScroll = false;
            frmMain.ClearChildren();
            frmMain.AddChild(pnl);
        }

        private static void ShowDialog(string Message)
        {
            API.SetModalState(true);
            int w, h, x, y, i;

            // Copy out the current buffer
            buffer2 = new Bitmap(AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            buffer2.DrawImage(0, 0, API._buffer, 0, 0, buffer2.Width, buffer2.Height);

            // First figure out how wide to make the window
            FontManager.Arial.ComputeExtent(PyxisAPI.Product, out w, out h);
            size sz = FontManager.ComputeExtentEx(FontManager.Arial, Message);
            if (sz.Width > w) w = sz.Width;

            // Add border
            w += 16;

            // Check Mimimum Width
            if (w < 76) w = 76;

            // Now make sure we're within the screen width
            if (w > AppearanceManager.ScreenWidth - 8) w = AppearanceManager.ScreenWidth - 8;

            // Get the height of the 2 strings combined
            // We already have the title height in h
            // Remove 16px for 2 borders + 2 spacers (4+4+4+4)
            FontManager.Arial.ComputeTextInRect(Message, out y, out i, w - 16);
            h = h + i;

            // Add borders and spacing 49px (4 border + 2 space + title + 2 space + 4 border = 4 space + text height + 4 space + CommandButton height + 4 space + 4 border)
            h = h + 20;

            // Make sure we're still on the screen
            if (h > AppearanceManager.ScreenHeight - 8) h = AppearanceManager.ScreenHeight - 8;

            // Center the window
            x = (int)(((float)AppearanceManager.ScreenWidth / 2) - ((float)w / 2));
            y = (int)(((float)AppearanceManager.ScreenHeight / 2) - ((float)h / 2));

            // We're ready to create the window
            Form frmLaunch = new Form(API, Colors.LightGray, x, y, w, h, true, true, Form.WindowType.fullborder);
            frmLaunch.AddChild(new Label(PyxisAPI.Product, Colors.White, 6, 5, w - 10, FontManager.Arial.Height));
            frmLaunch.AddChild(new Label(Message, Colors.Black, 8, 5 + FontManager.Arial.Height + 6, w - 16, h - 20 - FontManager.Arial.Height));

            // Set the Active Form (without rendering)
            API._ActiveForm = frmLaunch;

            // Draw Prompt
            API._buffer.DrawRectangle(Colors.Black, 0, 0, 0, buffer2.Width, buffer2.Height, 0, 0, Colors.Black, 0, 0, Colors.Black, 0, 0, 100);
            API._buffer.Flush();
            frmLaunch.Render();
        }

        private static void TextChanged(Textbox txtEmail, Textbox txtPassword, CommandButton cmdLogin)
        {
            cmdLogin.Enabled = (txtEmail.Text != string.Empty && txtPassword.Text != string.Empty) ? true : false;
        }

        #endregion

        #region Web Methods

        private static NetResponse DownloadURL(string url)
        {
            return DownloadURL(url, "Pyxis/2.0 (Pyxis; U; Pyxis 2.0; en-US; rv:2.0.0.5)");
        }

        private static NetResponse DownloadURL(string url, string useragent)
        {

            HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
            request.UserAgent = useragent;
            request.KeepAlive = false;
            request.Headers.Add("Cookie", _COOKIE);
            WebResponse resp = request.GetResponse();

            // Get the network response stream to read the page data.
            if (resp != null)
            {
                Stream respStream = resp.GetResponseStream();
                byte[] byteData;
                int bytesRead = 0;
                Decoder UTF8decoder = Encoding.UTF8.GetDecoder();
                int totalRead = 0;

                // allow 5 seconds for reading the stream
                respStream.ReadTimeout = 5000;

                // If we know the content length, read exactly that amount of 
                // data; otherwise, read until there is nothing left to read.
                if (resp.ContentLength != -1)
                {
                    byteData = new byte[resp.ContentLength];
                    for (int dataRem = (int)resp.ContentLength; dataRem > 0; )
                    {
                        Thread.Sleep(500);
                        bytesRead = respStream.Read(byteData, totalRead, byteData.Length);

                        if (bytesRead == 0)
                            throw new Exception("Data less than expected");

                        totalRead += bytesRead;
                        dataRem -= bytesRead;
                    }

                }
                else
                {
                    // Attempt to read chunks
                    byteData = new byte[10240];
                    string strOut = string.Empty;

                    while (true)
                    {
                        Thread.Sleep(500);

                        if (byteData.Length > respStream.Length)
                            byteData = new byte[respStream.Length];

                        bytesRead = respStream.Read(byteData, 0, byteData.Length);
                        if (bytesRead == 0)
                        {
                            byteData = UTF8Encoding.UTF8.GetBytes(strOut);
                            break;
                        }
                        else
                            strOut += new string(UTF8Encoding.UTF8.GetChars(byteData));

                    }

                }

                // Close the response stream.  For Keep-Alive streams, the 
                // stream will remain open and will be pushed into the unused 
                // stream list.
                resp.Close();
                request.Dispose();

                NetResponse NT = new NetResponse();
                string[] keys = resp.Headers.AllKeys;
                NT.Headers = new NetHeader[keys.Length];
                for (int i = 0; i < keys.Length; i++)
                    NT.Headers[i] = new NetHeader(keys[i], resp.Headers[keys[i]]);
                NT.Data = new string(UTF8Encoding.UTF8.GetChars(byteData));
                return NT;
            }


            return new NetResponse();
        }

        private static NetResponse DoPost(string url, string postData, string[] headers = null)
        {
            HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
            request.UserAgent = "Pyxis/2.0 (Pyxis; U; Pyxis 2.0; en-US; rv:2.0.0.5)";
            request.Method = "POST";
            request.Headers.Add("Cookie", _COOKIE);
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = postData.Length;
            if (headers != null)
            {
                for (int i = 0; i < headers.Length; i++)
                    request.Headers.Add(headers[i]);
            }

            using (Stream writeStream = request.GetRequestStream())
            {
                UTF8Encoding encoding = new UTF8Encoding();
                byte[] bytes = encoding.GetBytes(postData);
                writeStream.Write(bytes, 0, bytes.Length);
            }

            string result = string.Empty;
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (Stream responseStream = response.GetResponseStream())
                {
                    using (StreamReader readStream = new StreamReader(responseStream))
                    {
                        result = readStream.ReadToEnd();
                    }
                }


                NetResponse NT = new NetResponse();
                string[] keys = response.Headers.AllKeys;
                NT.Headers = new NetHeader[keys.Length];
                for (int i = 0; i < keys.Length; i++)
                    NT.Headers[i] = new NetHeader(keys[i], response.Headers[keys[i]]);
                NT.Data = result;
                return NT;
            }

        }

        #endregion

        #region Forum Methods

        private static void BoardList()
        {
            int i;
            string sData, sName, sDesc, sHref;
            bool hasNew;
            NetResponse sRes;

            AppMenu.Text = "TinyCLR (Loading)";

            frmMain.ClearChildren();
            FLB = new ForumListbox(0, 0, frmMain.Width, frmMain.Height);
            frmMain.AddChild(FLB);

            try
            {
                sRes = DownloadURL("http://www.tinyclr.com/ajax/site/forum/boardList.php");
            }
            catch (Exception)
            {
                try
                {
                    sRes = DownloadURL("http://www.tinyclr.com/ajax/site/forum/boardList.php");
                }
                catch (Exception)
                {
                    API.Prompt("Could not load board!", "TinyCLR Forum", PromptType.OKOnly);
                    return;
                }
            }
            //Debug.Print(sRes.Data);

            // Each board item is in a "board-info" class div
            // The name is in an href & the description is in a board-desc class div
            Debug.Print("Loading Boards...");
            sData = sRes.Data;

            while (true)
            {
                // Find the next div
                i = sData.IndexOf("class=\"board-icon\"");

                // Exit if none
                if (i < 0)
                    break;

                // Trim to div
                sData = sData.Substring(i);

                // Find the Icon
                sName = sData.Substring(0, sData.IndexOf("</div"));
                hasNew = (sName.IndexOf("NEW") > -1) ? true : false;

                // Trim to div
                sData = sData.Substring(sData.IndexOf("class=\"board-info\""));

                // Find the href
                sData = sData.Substring(sData.IndexOf("href=\"") + 6);

                // Find the end of the link
                i = sData.IndexOf('"');
                sHref = sData.Substring(0, i);
                sData = sData.Substring(i + 2);
                
                // Find the Text
                sName = sData.Substring(0, sData.IndexOf("</a>"));

                // Find the Description
                i = sData.IndexOf("class=\"board-desc\"");

                // Trim to div
                sData = sData.Substring(i + 19);
                sDesc = sData.Substring(0, sData.IndexOf("</div>"));

                // Add Item
                FLB.AddItem(new ForumListbox.ForumListItem(sName, sDesc, sHref, hasNew));
                

            }

            AppMenu.Text = "TinyCLR Forum";
            FLB.SelectedIndexChanged += new OnSelectedIndexChange((object sender, int e) => BoardItemClicked());
        }

        private static void BoardItemClicked()
        {
            ForumListbox.ForumListItem FLI = FLB.SelectedItem;

            string ForumID = FLI.URL.Substring(0, FLI.URL.Length - 1);
            ForumID = ForumID.Substring(ForumID.LastIndexOf('/') + 1);
            string URL = "http://www.tinyclr.com/ajax/site/forum/topicList.php?boardId=" + ForumID;
            TopicList(URL);
        }

        private static void TopicList(string URL)
        {
            int i;
            string sData, sName, sDesc, sHref;
            bool hasNew;
            NetResponse sRes;

            AppMenu.Text = "TinyCLR (Loading)";

            frmMain.ClearChildren();
            FLB = new ForumListbox(0, 0, frmMain.Width, frmMain.Height);
            frmMain.AddChild(FLB);

            try
            {
                sRes = DownloadURL(URL);
            }
            catch (Exception)
            {
                try
                {
                    sRes = DownloadURL(URL);
                }
                catch (Exception)
                {
                    API.Prompt("Could not load topic!", "TinyCLR Forum", PromptType.OKOnly);
                    return;
                }
            }

            // Each board item is in a "board-info" class div
            // The name is in an href & the description is in a board-desc class div
            Debug.Print("Loading Topics...");
            sData = sRes.Data;

            while (true)
            {
                // Find the next div
                i = sData.IndexOf("class=\"topic topic-bg \"");
               
                // Exit if none
                if (i < 0)
                    break;

                // Trim to innder div
                sData = sData.Substring(i + 10);
                i = sData.IndexOf("class=\"topic-icon\"");
                sData = sData.Substring(i + 10);

                // Find the Icon
                sName = sData.Substring(0, sData.IndexOf("</div"));
                hasNew = (sName.IndexOf("NEW") > -1) ? true : false;
                sData = sData.Substring(sName.Length);

                // Find the href
                sData = sData.Substring(sData.IndexOf("href=\"") + 6);

                // Find the end of the link
                i = sData.IndexOf('"');
                sHref = sData.Substring(0, i);
                sData = sData.Substring(i + 2);

                // Find the Text
                sName = StripHTML(sData.Substring(0, sData.IndexOf("</a>")));

                // Find the Description
                i = sData.IndexOf("class=\"topic-author-lastposter\"");

                // Trim to div
                sData = sData.Substring(i + 32);
                sDesc = StripHTML(sData.Substring(0, sData.IndexOf("</div>")));

                // Add Item
                FLB.AddItem(new ForumListbox.ForumListItem(sName, sDesc, sHref, hasNew));
            }

            AppMenu.Text = "TinyCLR Forum";
            FLB.SelectedIndexChanged += new OnSelectedIndexChange((object sender, int e) => TopicItemClicked());
        }

        private static void TopicItemClicked()
        {
            ForumListbox.ForumListItem FLI = FLB.SelectedItem;

            string[] sParts = FLI.URL.Split('/');
            string URL;

            if (sParts.Length < 6)
            {
                URL = "http://www.tinyclr.com/ajax/site/forum/messageList.php?boardId=" + sParts[2] + "&topicId=" + sParts[3];
            }
            else
            {
                URL = "http://www.tinyclr.com/ajax/site/forum/messageList.php?boardId=" + sParts[2] + "&topicId=" + sParts[3] + "&page=" + sParts[5];
            }

            PostView(URL);
        }

        private static void PostView(string URL)
        {
            int i;
            string sData, sName, sMsg, sIMG;
            NetResponse sRes;

            AppMenu.Text = "TinyCLR (Loading)";

            frmMain.ClearChildren();
            MLB = new MessageListbox(0, 0, frmMain.Width, frmMain.Height); 
            frmMain.AddChild(MLB);

            try
            {
                sRes = DownloadURL(URL);
            }
            catch (Exception)
            {
                try
                {
                    sRes = DownloadURL(URL);
                }
                catch (Exception)
                {
                    API.Prompt("Could not load topic!", "TinyCLR Forum", PromptType.OKOnly);
                    return;
                }
            }

            // Each board item is in a "board-info" class div
            // The name is in an href & the description is in a board-desc class div
            Debug.Print("Loading Thread...");
            sData = sRes.Data;
            //Debug.Print(sData);

            while (true)
            {
                // Find next message
                i = sData.IndexOf("class=\"msg\"");

                // Exit if none
                if (i < 0)
                    break;

                // Trim to div
                sData = sData.Substring(i + 11);

                // Find Poster
                sData = sData.Substring(sData.IndexOf("class=\"pad-top\"") + 16);
                i = sData.IndexOf("</div>");
                sName = StripHTML(sData.Substring(0, i));
                sName = PyxisAPI.StringReplace(sName, "\n", " ");
                sData = sData.Substring(i);

                // Find Message
                sData = sData.Substring(sData.IndexOf("class=\"msg-col-text "));
                sData = sData.Substring(sData.IndexOf(">") + 1);
                i = sData.IndexOf("</div>");
                sMsg = StripHTML(sData.Substring(0, i));
                sData = sData.Substring(i);

                // Find End
                i = sData.IndexOf("class=\"clear\"");
                
                // Find any Image
                //sIMG = PyxisAPI.StringReplace(sData.Substring(0, i), "href=\"#\"", "");
                sIMG = sData.Substring(0, i);
                i = sIMG.IndexOf("href=");
                if (i < 0)
                {
                    // No Image
                    sIMG = string.Empty;
                }
                else
                {
                    // Get Image
                    sIMG = sIMG.Substring(i + 6);
                    sIMG = sIMG.Substring(0, sIMG.IndexOf("\""));
                }

                //Debug.Print("Poster: " + sName);
                //Debug.Print(sMsg);
                //if (sIMG != string.Empty)
                //    Debug.Print("[IMG: " + sIMG + "]");
                //Debug.Print(" ");

                MLB.AddItem(new MessageListbox.MessageListItem(sName, sMsg, sIMG, new rect()));
            }


            AppMenu.Text = "TinyCLR Forum";
            FLB.SelectedIndexChanged += null; // new OnSelectedIndexChange((object sender, int e) => TopicItemClicked());
        }

        private static string StripHTML(string sValue)
        {
            int i, e;

            sValue = PyxisAPI.StringReplace(sValue, "<br />", "\n");

            while (true)
            {
                i = sValue.IndexOf('<');

                if (i < 0)
                    break;

                e = sValue.IndexOf('>', i);
                sValue = sValue.Substring(0, i) + sValue.Substring(e + 1);

            }

            while (sValue.IndexOf("  ") >= 0)
                sValue = PyxisAPI.StringReplace(sValue, "  ", " ");
            sValue = PyxisAPI.StringReplace(sValue, "&quot;", "\"");
            sValue = PyxisAPI.StringReplace(sValue, "&amp;", ";");

            return sValue.Trim();
        }

        #endregion

        #region Custom Controls

        private class ForumListbox : Control
        {

            #region Structures

            public struct ForumListItem
            {
                public string Name;
                public string Desc;
                public string URL;
                public bool IsNew;
                public ForumListItem(string Name, string Desc, string URL, bool IsNew)
                {
                    this.Name = Name;
                    this.Desc = Desc;
                    this.URL = URL;
                    this.IsNew = IsNew;
                }
            }

            #endregion

            #region Variables

            private int _mY = 0;
            private bool _bMoved = false;

            private int _iSel = -1;
            private Color _bkg = Colors.Black;
            private ArrayList _items = new ArrayList();
            private Scrollbar VScroll;

            #endregion

            #region Constructors

            public ForumListbox(int X, int Y, int Width, int Height)
            {
                _x = X;
                _y = Y;
                _w = Width;
                _h = Height;

                VScroll = new Scrollbar(_w - 16, 0, Height, Orientation.Vertical);
                VScroll.SmallChange = 40;
                VScroll.LargeChange = 80;
                VScroll.Enabled = false;
                VScroll.Parent = this;
                VScroll.ValueChanged += new OnValueChanged((object sender, int value) => Render(true));
            }

            #endregion

            #region Events

            public event OnSelectedIndexChange SelectedIndexChanged;

            /// <summary>
            /// Event for Taps
            /// </summary>
            /// <param name="sender"></param>
            protected virtual void OnSelectedIndexChange(Object sender, int e)
            {
                if (SelectedIndexChanged != null) SelectedIndexChanged(sender, e);
            }

            #endregion

            #region Touch Invokes

            public override void TouchDown(object sender, point e)
            {
                if (!_enabled)
                    return;

                if (e.X >= _w - 16)
                {
                    VScroll.TouchDown(sender, e);
                    return;
                }
                else
                {
                    _mY = e.Y;
                    _mDown = true;
                }
            }

            public override void TouchUp(object sender, point e)
            {
                if (!_enabled)
                    return;

                if (_mDown && e.X <= _w - 16)
                {
                    if (!_bMoved)
                    {
                        int y = e.Y - Top + VScroll.Value;
                        int index = 0;
                        while (y > 40)
                        {
                            y -= 40;
                            index++;
                        }
                        if (index <= _items.Count)
                        {
                            SelectedIndex = index;
                        }
                    }
                }

                VScroll.TouchUp(sender, e);
                _bMoved = false;
            }

            public override void TouchMove(object sender, point e)
            {
                if (!_enabled)
                    return;

                if (e.X >= _w - 16)
                {
                    VScroll.TouchMove(sender, e);
                    return;
                }

                _bMoved = true;
                VScroll.Value -= (e.Y - _mY);
            }

            #endregion

            #region Properties

            public int SelectedIndex
            {
                get { return _iSel; }
                set
                {
                    if (_iSel == value)
                        return;

                    _iSel = value;
                    OnSelectedIndexChange(this, _iSel);
                }
            }

            public ForumListItem SelectedItem
            {
                get { return (ForumListItem)_items[_iSel]; }
            }

            #endregion

            #region Public Methods

            public void AddItem(ForumListItem item)
            {
                _items.Add(item);

                int iH = _items.Count * 40;
                if (iH > _h - 2)
                {
                    VScroll.Maximum = iH - _h - 2;
                    VScroll.Enabled = true;
                }

                Render(true);
            }

            public void Clear()
            {
                VScroll.Enabled = false;
                _items.Clear();
                Render(true);
            }

            public override void SetOffset(Control sender, point e)
            {
                base.SetOffset(sender, e);
                VScroll.SetOffset(this, e);
            }

            #endregion

            #region GUI

            public override void Render(bool flush)
            {
                if (!_visible || _parent == null || _parent.ScreenBuffer == null || _suspend) return;

                // Set clipping region in case we're off parent somewhere (can happen w/ scroll)
                _parent.ScreenBuffer.SetClippingRectangle(_parent.Left, _parent.Top, _parent.Width, _parent.Height);

                _parent.ScreenBuffer.DrawRectangle(Color.Black, 1, Left, Top, _w, _h, 0, 0, _bkg, 0, 0, _bkg, 0, 0, 256);

                rect Region = rect.intersect(new rect(_parent.Left, _parent.Top, _parent.Width, _parent.Height), new rect(Left , Top + 1 , _w, _h - 2));
                _parent.ScreenBuffer.SetClippingRectangle(Region.X, Region.Y, Region.Width, Region.Height);

                int y = Top + 2 - VScroll.Value;
                int x = Left + 2;
                int nW = FontManager.ComputeExtentEx(FontManager.ArialBold, "[NEW]  ").Width;
                int lW = _w - 20;
                ForumListItem FLI;
                Color cNew = ColorUtility.ColorFromRGB(178, 0, 48);
                Color cNam = ColorUtility.ColorFromRGB(15, 163, 224);
                Color cBk1 = Colors.Black;
                Color cBk2 = ColorUtility.ColorFromRGB(45, 45, 45);
                Color cBkg = cBk1;

                for (int i = 0; i < _items.Count; i++)
                {
                    FLI = (ForumListItem)_items[i];

                    // Fill Background
                    _parent.ScreenBuffer.DrawRectangle(0, 0, x- 2, y, lW + 4, 40, 0, 0, cBkg, 0, 0, cBkg, 0, 0, 256);
                    cBkg = (cBkg == cBk1) ? cBk2 : cBk1;

                    // Name & New
                    if (FLI.IsNew)
                    {
                        _parent.ScreenBuffer.DrawText("[NEW]", FontManager.ArialBold, cNew, x, y + 2);
                        _parent.ScreenBuffer.DrawText(FLI.Name, FontManager.ArialBold, cNam, x + nW, y + 2);
                    }
                    else
                    {
                        _parent.ScreenBuffer.DrawText(FLI.Name, FontManager.ArialBold, cNam, x, y + 2);
                    }
                    y += 18;

                    _parent.ScreenBuffer.DrawTextInRect(FLI.Desc, x, y, lW, 20, Bitmap.DT_TrimmingCharacterEllipsis, Colors.White, FontManager.Arial);
                    y += 22;

                }

                VScroll.Render(false);

                if (flush)
                {
                    _parent.ScreenBuffer.SetClippingRectangle(0, 0, AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
                    _parent.ScreenBuffer.Flush(Left, Top, _w, _h);
                }
            }

            #endregion

        }

        private class MessageListbox : Control
        {

            #region Structures

            public struct MessageListItem
            {
                public string Poster;
                public string Message;
                public string IMGURL;
                public rect Bounds;
                public MessageListItem(string Poster, string Message, string IMGURL, rect Bounds)
                {
                    this.Poster = Poster;
                    this.Message = Message;
                    this.IMGURL = IMGURL;
                    this.Bounds = Bounds;
                }
            }

            #endregion

            #region Variables

            private int _mY = 0;
            private bool _bMoved = false;

            private int _iSel = -1;
            private Color _bkg = Colors.Black;
            private ArrayList _items = new ArrayList();
            private Scrollbar VScroll;
            private int maxY = 0;

            #endregion

            #region Constructors

            public MessageListbox(int X, int Y, int Width, int Height)
            {
                _x = X;
                _y = Y;
                _w = Width;
                _h = Height;

                VScroll = new Scrollbar(_w - 16, 0, Height, Orientation.Vertical);
                VScroll.SmallChange = 40;
                VScroll.LargeChange = 80;
                VScroll.Enabled = false;
                VScroll.Parent = this;
                VScroll.ValueChanged += new OnValueChanged((object sender, int value) => Render(true));
            }

            #endregion

            #region Events

            public event OnSelectedIndexChange SelectedIndexChanged;

            /// <summary>
            /// Event for Taps
            /// </summary>
            /// <param name="sender"></param>
            protected virtual void OnSelectedIndexChange(Object sender, int e)
            {
                if (SelectedIndexChanged != null) SelectedIndexChanged(sender, e);
            }

            #endregion

            #region Touch Invokes

            public override void TouchDown(object sender, point e)
            {
                if (!_enabled)
                    return;

                if (e.X >= _w - 16)
                {
                    VScroll.TouchDown(sender, e);
                    return;
                }
                else
                {
                    _mY = e.Y;
                    _mDown = true;
                }
            }

            public override void TouchUp(object sender, point e)
            {
                if (!_enabled)
                    return;

                if (_mDown && e.X <= _w - 16)
                {
                    if (!_bMoved)
                    {
                        int y = e.Y - Top + VScroll.Value;
                        int index = 0;
                        while (y > 40)
                        {
                            y -= 40;
                            index++;
                        }
                        if (index <= _items.Count)
                        {
                            SelectedIndex = index;
                        }
                    }
                }

                VScroll.TouchUp(sender, e);
                _bMoved = false;
            }

            public override void TouchMove(object sender, point e)
            {
                if (!_enabled)
                    return;

                if (e.X >= _w - 16)
                {
                    VScroll.TouchMove(sender, e);
                    return;
                }

                _bMoved = true;
                VScroll.Value -= (e.Y - _mY);
            }

            #endregion

            #region Properties

            public int SelectedIndex
            {
                get { return _iSel; }
                set
                {
                    if (_iSel == value)
                        return;

                    _iSel = value;
                    OnSelectedIndexChange(this, _iSel);
                }
            }

            public MessageListItem SelectedItem
            {
                get { return (MessageListItem)_items[_iSel]; }
            }

            #endregion

            #region Public Methods

            public void AddItem(MessageListItem item)
            {
                int tW, tH;
                FontManager.Arial.ComputeTextInRect(item.Message, out tW, out tH, _w - 24);
                item.Bounds = new rect(0, 0, _w - 24, tH + ((item.IMGURL == string.Empty) ? 26 : 52));
                _items.Add(item);
                maxY += item.Bounds.Height;

                if (maxY > _h - 2)
                {
                    VScroll.Maximum = maxY - _h - 2;
                    VScroll.Enabled = true;
                }

                Render(true);
            }

            public void Clear()
            {
                maxY = 0;
                VScroll.Enabled = false;
                _items.Clear();
                Render(true);
            }

            public override void SetOffset(Control sender, point e)
            {
                base.SetOffset(sender, e);
                VScroll.SetOffset(this, e);
            }

            #endregion

            #region GUI

            public override void Render(bool flush)
            {
                if (!_visible || _parent == null || _parent.ScreenBuffer == null || _suspend) return;

                // Set clipping region in case we're off parent somewhere (can happen w/ scroll)
                _parent.ScreenBuffer.SetClippingRectangle(_parent.Left, _parent.Top, _parent.Width, _parent.Height);

                _parent.ScreenBuffer.DrawRectangle(Color.Black, 1, Left, Top, _w, _h, 0, 0, _bkg, 0, 0, _bkg, 0, 0, 256);

                rect Region = rect.intersect(new rect(_parent.Left, _parent.Top, _parent.Width, _parent.Height), new rect(Left, Top + 1, _w, _h - 2));
                _parent.ScreenBuffer.SetClippingRectangle(Region.X, Region.Y, Region.Width, Region.Height);

                int y = Top + 2 - VScroll.Value;
                int y2;
                int x = Left + 2;
                int nW = FontManager.ComputeExtentEx(FontManager.ArialBold, "[NEW]  ").Width;
                int lW = _w - 20;
                //ForumListItem FLI;
                MessageListItem MLI;
                Color cNew = ColorUtility.ColorFromRGB(178, 0, 48);
                Color cNam = ColorUtility.ColorFromRGB(15, 163, 224);
                Color cBk1 = Colors.Black;
                Color cBk2 = ColorUtility.ColorFromRGB(45, 45, 45);
                Color cBkg = cBk1;

                for (int i = 0; i < _items.Count; i++)
                {
                    MLI = (MessageListItem)_items[i];
                    _parent.ScreenBuffer.DrawRectangle(0, 0, x - 2, y, lW + 4, MLI.Bounds.Height, 0, 0, cBkg, 0, 0, cBkg, 0, 0, 256);
                    cBkg = (cBkg == cBk1) ? cBk2 : cBk1;

                    _parent.ScreenBuffer.DrawTextInRect(MLI.Poster, x + 2, y + 2, MLI.Bounds.Width - 4, 20, Bitmap.DT_TrimmingCharacterEllipsis, cNam, FontManager.ArialBold);

                    _parent.ScreenBuffer.DrawTextInRect(MLI.Message, x + 2, y + 22, MLI.Bounds.Width - 4, MLI.Bounds.Height - ((MLI.IMGURL == string.Empty) ? 26 : 52), Bitmap.DT_WordWrap, Colors.White, FontManager.Arial);

                    if (MLI.IMGURL != string.Empty)
                    {
                        y2 = y + MLI.Bounds.Height - 22;
                        _parent.ScreenBuffer.DrawText("Attached Image", FontManager.Arial, Colors.Blue, x + 2, y2);
                        y2 += FontManager.Arial.Height;
                        _parent.ScreenBuffer.DrawLine(Colors.Blue, 1, x + 2, y2, x + 2 + FontManager.ComputeExtentEx(FontManager.Arial, "Attached Image").Width, y2);
                    }

                    y += MLI.Bounds.Height;
                }

                VScroll.Render(false);

                if (flush)
                {
                    _parent.ScreenBuffer.SetClippingRectangle(0, 0, AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
                    _parent.ScreenBuffer.Flush(Left, Top, _w, _h);
                }
            }

            #endregion

        }

        #endregion

    }
}
